﻿using System.Collections.Generic;
using System.Text;
using System;
using System.Xml.Serialization;
using System.IO;
using System.ComponentModel;

namespace EmbeddedLanguage
{
    public class NestedString //: ICanDoNew<NestedString>
    {

        public NestedString() { }

        //#region ICanDo
        //[XmlIgnore]
        //public ActionList<NestedString> OnNew { set { value.ForEach(action => action(this)); } }
        //public NestedString Do(List<Action<NestedString>> actions) { actions.ForEach(action => action(this)); return this; }
        //#endregion
        [XmlIgnore]
        public StringBuilder sb;
        private NestedString _Parent;
        [XmlIgnore]
        public NestedString Parent
        {
            get
            {
                return _Parent;
            }
            set
            {
                _Parent = value;
                _Parent.ChildrenNN.Add(this);
            }
        }
        [XmlAttribute]
        public string Header;
        [XmlAttribute]
        public string Footer;
        [XmlAttribute]
        public string LHS;
        [XmlAttribute]
        public string RHS;
        [XmlAttribute]
        public string Waist;
        #region Templates
        [XmlAttribute]
        public string Header_Template;
        [XmlAttribute]
        public string Footer_Template;
        [XmlAttribute]
        public string LHS_Template;
        [XmlAttribute]
        public string RHS_Template;
        [XmlAttribute]
        public string Waist_Template;
        #endregion
        [XmlAttribute]
        public string Comment;
        [DefaultValue("")]
        [XmlAttribute]
        public string ChildSeparator = string.Empty;
        [DefaultValue("")]
        [XmlAttribute]
        public string ChildSeparatorBeforeFirstElement = string.Empty;
        [DefaultValue("")]
        [XmlAttribute]
        public string ChildSeparatorFollowingLastElement = string.Empty;

        [DefaultValue(false)]
        [XmlAttribute]
        public bool ChildrenContinueOnLine;
        [XmlAttribute]
        [DefaultValue(false)]
        public bool ForceLineBreakOnRHS;
        [DefaultValue(false)]
        [XmlAttribute]
        public bool ForceLineBreakOnLHS;
        [XmlAttribute]
        [DefaultValue(false)]
        public bool TreatRHSLikeBeginningOfFooter;
        [XmlAttribute]
        [DefaultValue(false)]
        public bool ForceLineBreakOnFooter;
        [XmlAttribute]
        [DefaultValue(false)]
        public bool ForceLineBreakOnWaist;
        [XmlAttribute]
        [DefaultValue(false)]
        public bool ForceLineBreakOnEachChild;
        [XmlIgnore]
        public Dictionary<string, int> ChildSubstitutions;
        [XmlIgnore]
        public Dictionary<string, int> ChildSubstitutionsNN
        {
            get
            {
                if (ChildSubstitutions == null) ChildSubstitutions = new Dictionary<string, int>();
                return ChildSubstitutions;
            }
        }
        [XmlIgnore]
        public List<int> IgnoredChildren;
        [XmlIgnore]
        public List<int> IgnoredChildrenNN
        {
            get
            {
                if (IgnoredChildren == null) IgnoredChildren = new List<int>();
                return IgnoredChildren;
            }
        }
        [XmlAttribute]
        [DefaultValue(false)]
        public bool RemoveTrailingSemiColonBeforeRHS;
        [XmlAttribute("ChildSubstitutions")]
        public string ChildSubstitutionsXML
        {
            get
            {
                if (ChildSubstitutions == null) return null;
                StringBuilder sb = new StringBuilder();
                foreach (var kvp in ChildSubstitutions)
                {
                    sb.Append(kvp.Value + "=>" + kvp.Key + ";");
                }
                return sb.ToString();
            }
            set { throw new NotImplementedException(); }
        }

        public List<NestedString> Children;
        [XmlIgnore]
        public List<NestedString> ChildrenNN
        {
            get
            {
                if (Children == null) Children = new List<NestedString>();
                return Children;
            }
        }
        [XmlIgnore]
        public NestedString LastChild
        {
            get
            {
                if (Children == null) return null;
                return Children[Children.Count - 1];
            }
        }
        [XmlAttribute]
        public string TrainOfMethodsParameter;
        [DefaultValue(false)]
        [XmlAttribute]
        public bool IsKaboose;
        [XmlAttribute]
        [DefaultValue(false)]
        public bool IsKabooseTransferrableToChild;

        [XmlIgnore]
        public NestedString ReturningParent;

        #region Has Queries
        [XmlIgnore]
        public bool HasHeader
        {
            get
            {
                return !string.IsNullOrEmpty(this.Header) || !string.IsNullOrEmpty(this.Header_Template);
            }
        }
        [XmlIgnore]
        public bool HasLHS
        {
            get
            {
                return !string.IsNullOrEmpty(this.LHS) || !string.IsNullOrEmpty(this.LHS_Template);
            }
        }
        [XmlIgnore]
        public bool HasRHS
        {
            get
            {
                return !string.IsNullOrEmpty(this.RHS) || !string.IsNullOrEmpty(this.RHS_Template);
            }
        }
        [XmlIgnore]
        public bool HasFooter
        {
            get
            {
                return !string.IsNullOrEmpty(this.Footer) || !string.IsNullOrEmpty(this.Footer_Template);
            }
        }
        [XmlIgnore]
        public bool HasWaist
        {
            get
            {
                return !string.IsNullOrEmpty(this.Waist) || !string.IsNullOrEmpty(this.Waist_Template);
            }
        }
        #endregion

        [XmlIgnore]
        public string DebugVal
        {
            get
            {
                return this.GetString();
            }
        }

        public bool ToString(int Level, bool ContinueLine, StringBuilder sb)
        {
            int localLevel = Level;
            bool localContinueLine = ContinueLine;
            #region throw away ignored children
            this.IgnoredChildren.DoForEach(i =>
            {
                this.Children[i] = null;
            });
            #endregion
            #region Get Children First in case they need to be substituted
            List<string> ChildStrings = null;
            if (this.HasHeader)
            {
                localLevel++;
                localContinueLine = false;
            }
            if (this.HasLHS)
            {
                localContinueLine = true;
            }
            if (this.Children != null && this.Children.Count != 0)
            {
                ChildStrings = new List<string>();
                bool firstChild = false;
                this.Children.DoForEach(child =>
                {
                    if (child != null)
                    {
                        string childString = this.ChildrenContinueOnLine ?
                            child.ToString(localLevel, true) :
                            child.ToString(localLevel, localContinueLine);

                        ChildStrings.Add(childString);
                        
                    }
                    else
                    {
                        ChildStrings.Add(string.Empty);
                    }
                    firstChild = false;
                });
            }

            localLevel = Level;
            localContinueLine = ContinueLine;

            #endregion
            #region Check for Child Substitions
            
            if (this.ChildSubstitutions != null)
            {
                List<int> itemsToRemove = new List<int>();
                foreach (var substitution in this.ChildSubstitutions)
                {
                    int idx = substitution.Value;
                    if (idx < ChildStrings.Count)
                    {
                        itemsToRemove.Add(idx);
                        string newVal = ChildStrings[idx];
                        string search = substitution.Key;

                        if(this.Header_Template != null && this.Header_Template.Contains(search))
                        {
                            this.Header = (this.Header ?? this.Header_Template).Replace(search, newVal);
                        }
                        if (this.LHS_Template != null && this.LHS_Template.Contains(search))
                        {
                            this.LHS = (this.LHS ?? this.LHS_Template).Replace(search, newVal);
                        }
                        if (this.RHS_Template != null && this.RHS_Template.Contains(search))
                        {
                            this.RHS = (this.RHS ?? this.RHS_Template).Replace(search, newVal);
                        }
                        if (this.Footer_Template != null && this.Footer_Template.Contains(search))
                        {
                            this.Footer = (this.Footer ?? this.Footer_Template).Replace(search, newVal);
                        }
                        if (this.Footer_Template != null) this.Footer = this.Footer_Template.Replace(search, newVal);
                    }
                }
                itemsToRemove.Sort();
                itemsToRemove.Reverse();
                foreach (int idx in itemsToRemove)
                {
                    ChildStrings.RemoveAt(idx);
                }
                
            }
            #endregion

            #region Render Header
            if (this.HasHeader)
            {
                sb.LF(localContinueLine, localLevel);
                sb.Append(this.Header);
                localContinueLine = false;
                localLevel++;
            }
            #endregion
            #region RenderLHS
            if (this.HasLHS)
            {
                sb.LF(localContinueLine && !this.ForceLineBreakOnLHS, localLevel);
                if (this.RemoveTrailingSemiColonBeforeRHS && (ChildStrings == null || ChildStrings.Count==0) && this.LHS.EndsWith(";"))
                {
                    this.LHS = this.LHS.SubstringBeforeLast(";");
                }
                sb.Append(this.LHS);
                localContinueLine = true;
                
            }
            #endregion
            #region RenderChildren
            if (ChildStrings != null && ChildStrings.Count != 0)
            {
                if (this.ForceLineBreakOnEachChild)
                {
                    int cnt = 0;
                    int len = ChildStrings.Count;
                    bool firstChild = true;
                    foreach (string child in ChildStrings)
                    {
                        if (firstChild)
                        {
                            sb.LF(this.Parent.ChildrenContinueOnLine && localContinueLine, localLevel);
                            sb.Append(this.ChildSeparatorBeforeFirstElement);
                            firstChild = false;

                        }
                        else
                        {
                            sb.LF(false, localLevel);
                        }
                        sb.Append(child);
                        cnt++;
                        if (cnt == len)
                        {
                            sb.Append(this.ChildSeparatorFollowingLastElement);
                        }
                        else
                        {
                            sb.Append(this.ChildSeparator);
                        }
                    }
                }
                else
                {
                    var childrenString = String.Join(this.ChildSeparator, ChildStrings.ToArray());
                    sb.Append(this.ChildSeparatorBeforeFirstElement);
                    sb.Append(childrenString);
                    sb.Append(this.ChildSeparatorFollowingLastElement);
                }
            }
            #endregion
            #region Render Waist
            if (this.Waist != null)
            {
                if (this.Header != null)
                {
                    localLevel--;
                    localContinueLine = false;
                }
                else if (this.ForceLineBreakOnWaist)
                {
                    localContinueLine = false;
                }
                sb.LF(localContinueLine, localLevel);
                sb.Append(this.Waist);
                localLevel++;
            }
            #endregion
            if (this.HasRHS)
            {
                
                if (this.ForceLineBreakOnRHS || this.TreatRHSLikeBeginningOfFooter)
                {
                    localContinueLine = false;
                    if (this.TreatRHSLikeBeginningOfFooter)
                    {
                        localLevel--;
                    }
                }
                sb.LF(localContinueLine, localLevel);
                sb.Append(this.RHS);
                localContinueLine = true;
            }
            
            if (this.HasFooter)
            {
                if (this.HasHeader || this.HasWaist)
                {
                    if (!this.TreatRHSLikeBeginningOfFooter)
                    {
                        localLevel--;
                    }
                    localContinueLine = false;
                }
                else if (this.ForceLineBreakOnFooter)
                {
                    localContinueLine = false;
                }
                sb.LF(localContinueLine, localLevel);
                sb.Append(this.Footer);
            }
            return ContinueLine;
        }
        
        [XmlIgnore]
        public string Xml
        {
            get
            {
                XmlSerializer xs = new XmlSerializer(typeof(NestedString));
                StringWriter sw = new StringWriter();
                xs.Serialize(sw, this);
                return sw.ToString();
            }
        }

        public string GetString()
        {
            return this.ToString(0, true);
        }

        public string ToString(int Level, bool ContinueLine)
        {
            StringBuilder sb = new StringBuilder();
            this.ToString(Level, ContinueLine, sb);
            return sb.ToString();
        }

        [XmlIgnore]
        public NestedString Root
        {
            get
            {
                var root = this;
                while (root.Parent != null)
                {
                    root = root.Parent;
                }
                return root;
            }
        }
    }
}
